﻿using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using log4net;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NComputerVision.Learning;
using NComputerVision.Learning.AdaBoost;
using NComputerVision.Learning.K_NN;
using NComputerVision.Learning.NeuralNet;
using NComputerVision.Learning.SVM;

namespace NComputerVision.Test
{
    /// <summary>
    ///This is a test class for UtilityTest and is intended
    ///to contain all UtilityTest Unit Tests
    ///</summary>
    [TestClass()]
    public partial class PRTest
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(PRTest));

        #region Additional test attributes
        // 
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //Use ClassCleanup to run code after all tests in a class have run
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //Use TestInitialize to run code before running each test
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        #region SVM

        [TestMethod()]
        //[Ignore("Ignore as Binary_SVM_SMO_Test is time-consuming.")]
        public void Binary_SVM_SMO_Test()
        {
            log.Info("Binary_SVM_SMO_Test");

            int hit = 0;
            double correctRatio;

            Problem problem = ProblemFactory.CreateClassificationProblem(ClassificationProblemType.ChessBoard);

            Collection<Example> t_set = problem.TrainingSet;
            Collection<Example> v_set = problem.ValidationSet;

            Binary_SVM_SMO classifier = new Binary_SVM_SMO(problem);
            classifier.TrainSet = t_set;
            classifier.Kernel = new GaussianRBFKernel(0.001);

            classifier.Train();

            log.Info("Doing cross-validation.");

            foreach (Example e in v_set)
            {
                int iResult = classifier.Predict(e.X);
                if (e.Label.Id == iResult)
                {
                    hit++;
                }
            }

            correctRatio = 1.0 * hit / v_set.Count;

            Assert.IsTrue(correctRatio > 0.950, string.Format("SVM-SMO (2-class) Correct Ratio, expected: greater than 0.970, actual: {0}.", correctRatio));
        }

        #endregion


        #region KNN

        [TestMethod()]
        public void KNN_Test()
        {
            log.Info("KNN_Test");

            int hit = 0;
            double correctRatio;

            Problem problem = ProblemFactory.CreateClassificationProblem(ClassificationProblemType.ChessBoard);
            KNN classifier;

            log.Info("Loading training data.");

            Collection<Example> t_set = problem.TrainingSet;
            Collection<Example> v_set = problem.ValidationSet;

            classifier = new KNN();
            classifier.KNN_K = 7;
            classifier.TrainSet = t_set;
            classifier.Train();

            log.Info("Doing cross-validation.");

            foreach (Example e in v_set)
            {
                int iResult = classifier.Predict(e.X);
                if (e.Label.Id == iResult)
                {
                    hit++;
                }
            }

            correctRatio = 1.0 * hit / v_set.Count;

            log.Info("CorrectRatio: {0}", correctRatio);

            Assert.IsTrue(correctRatio > 0.930, string.Format("KNN (2-class) Correct Ratio, expected: greater than 0.930, actual: {0}.", correctRatio));
        }

        #endregion


        #region ANN_BP

        [TestMethod()]
        //[Ignore("Not ready for daily execution.")]
        public void ANN_BP_Test()
        {
            log.Info("ANN_BP_Test");

            int hit = 0;
            double correctRatio;

            Problem problem = ProblemFactory.CreateClassificationProblem(ClassificationProblemType.ChessBoard);
            ANN_BP classifier;

            log.Info("Loading training data.");

            Collection<Example> t_set = problem.TrainingSet;
            Collection<Example> v_set = problem.ValidationSet;

            classifier = new ANN_BP(problem.Dimension);
            classifier.TrainSet = t_set;
            classifier.MaximumIteration = Int32.MaxValue;
            classifier.ANN_Eta = 0.5;
            classifier.ANN_Epsilon = 1e-3;
            classifier.LogInterval = 1000;
            classifier.Train();

            log.Info("Doing cross-validation.");

            foreach (Example e in v_set)
            {
                int iResult = classifier.Predict(e.X);

                if (e.Label.Id == iResult)
                {
                    hit++;
                }
            }

            correctRatio = 1.0 * hit / v_set.Count;

            log.Info("CorrectRatio: {0}", correctRatio);

            Assert.IsTrue(correctRatio > 0.900, string.Format("ANN_BP (2-class) Correct Ratio, expected: greater than 0.930, actual: {0}.", correctRatio));
        }

        #endregion


        #region AdaBoost

        [TestMethod()]
        public void AdaBoost_Test()
        {
            log.Info("AdaBoost_Test");

            int hit = 0;
            double correctRatio;

            Problem problem = ProblemFactory.CreateClassificationProblem(ClassificationProblemType.ChessBoard);
            AdaBoost classifier;

            Collection<Example> t_set = problem.TrainingSet;
            Collection<Example> v_set = problem.ValidationSet;

            classifier = new AdaBoost(t_set, 1000, problem.Dimension);
            classifier.Train();

            foreach (Example e in v_set)
            {
                int iResult = classifier.Predict(e.X);

                if (e.Label.Id == iResult)
                {
                    hit++;
                }
            }

            correctRatio = 1.0 * hit / v_set.Count;

            log.Info("CorrectRatio: {0}", correctRatio);

            Assert.IsTrue(correctRatio > 0.900, string.Format("AdaBoost (2-class) Correct Ratio, expected: greater than 0.900, actual: {0}.", correctRatio));
        }

        #endregion
    }

    public enum ClassificationProblemType
    {
        ChessBoard,
        Text,
    }

    public static class ProblemFactory
    {
        public static Problem CreateClassificationProblem(ClassificationProblemType problemType)
        {
            switch (problemType)
            {
                case ClassificationProblemType.ChessBoard:
                    return CreateChessBoard();
                case ClassificationProblemType.Text:
                    return CreateText();
                default:
                    throw new ArgumentException();
            }

        }

        #region ChessBoard

        private static Problem CreateChessBoard()
        {
            Problem problem = new Problem();

            Collection<Category> collect = new Collection<Category>();
            collect.Add(new Category(+1, "+1"));
            collect.Add(new Category(-1, "-1"));

            problem.Dimension = 2;
            problem.CategoryCollection = collect;
            problem.TrainingSet = GetExamples(collect);
            problem.ValidationSet = GetExamples(collect);


            return problem;

        }

        /// <summary>
        /// foamliu, 2009/04/15, 生成样本.
        /// </summary>
        /// <param name="set"></param>
        private static Collection<Example> GetExamples(Collection<Category> collect)
        {
            const int Rows = 4;
            const int Columns = 4;
            const int CellWidth = 100;
            const int CellHeight = 100;
            const int ExampleNumber = 640;

            Collection<Example> set = new Collection<Example>();
            set.Clear();
            Random rand = new Random();

            for (int i = 0; i < ExampleNumber; i++)
            {
                int x = (int)(rand.NextDouble() * Columns * CellWidth);
                int y = (int)(rand.NextDouble() * Rows * CellHeight);

                Example e = new Example();
                e.X = new SparseVector(2);
                e.X[0] = x;
                e.X[1] = y;
                e.Label = GetCategoryById(collect,
                    GetCat(x, y, CellWidth, CellHeight));

                set.Add(e);
            }

            return set;
        }

        private static int GetCat(int x, int y, int cellWidth, int cellHeight)
        {
            int toCheck = x / cellWidth + y / cellHeight;
            if ((toCheck & 1) == 0)
                return +1;
            else
                return -1;
        }


        #endregion


        #region Private Methods

        /// <summary>
        /// foamliu, 2009/12/21, please make sure you've uncompressed "2_newsgroups.7z" in the "data" folder.
        /// </summary>
        /// <returns></returns>
        private static Problem CreateText()
        {
            const string DataFolder = @"..\data\2_newsgroups";

            Problem problem = new Problem();

            Collection<Example> t_set = new Collection<Example>();
            Collection<Example> v_set = new Collection<Example>();

            Collection<Category> collect = new Collection<Category>();
            collect.Add(new Category(+1, "+1"));
            collect.Add(new Category(-1, "-1"));

            problem.Dimension = 2;
            problem.CategoryCollection = collect;

            DirectoryInfo dataFolder = new DirectoryInfo(DataFolder);
            DirectoryInfo[] subfolders = dataFolder.GetDirectories();
            int count = 0;

            for (int i = 0; i < subfolders.Count(); i++)
            {
                DirectoryInfo categoryFolder = subfolders[i];
                int cat = i * 2 - 1;
                // for all the text files in each category
                FileInfo[] files = categoryFolder.GetFiles();

                count = 0;
                int trainSetCount = Convert.ToInt32(Constants.TrainingSetRatio * files.Count());
                for (int j = 0; j < files.Count(); j++)
                {
                    FileInfo textFile = files[j];
                    Example e = new Example();

                    if (++count < trainSetCount)
                    {
                        t_set.Add(e);
                    }
                    else
                    {
                        v_set.Add(e);
                    }


                }
            }

            problem.TrainingSet = t_set;
            problem.ValidationSet = v_set;


            return problem;
        }

        private static void BuildExample(TextExample example, Vocabulary voc, int exampleCount)
        {
            int dimension = voc.Count;
            SparseVector vector = new SparseVector(dimension);

            foreach (string word in example.Tokens.Keys)
            {
                int pos = voc.GetWordPosition(word);
                if (pos == Constants.KEY_NOT_FOUND)
                    continue;

                // phi i(x) = tfi log(idfi) /k
                // tfi:     number of occurences of the term i in the document x
                // idfi:    the ratio between the total number of documents and the 
                //              number of documents containing the term
                // k:       normalisation constant ensuring that ||phi|| = 1 
                double phi = example.Tokens[word] * Math.Log(exampleCount / voc.WordExampleOccurMap[word]);
                vector.Components.Add(pos, phi);

            }
            vector.Normalize();
            example.X = vector;
        }

        private static Category GetCategoryById(Collection<Category> collect, int id)
        {
            foreach (Category c in collect)
            {
                if (c.Id == id)
                {
                    return c;
                }
            }
            throw new ApplicationException("Category not found.");
        }

        #endregion
    }
}
